const parcel = remote.getGlobal('parcel');
const rsa = require('node-rsa');
const crypto = require('crypto-js');

function startConnection() {
	parcel.reconnect = true;
	let server = getServer();
	let host = server.addr;
	let port = server.port;
	let secure = server.secure;
	let url = `${secure ? 'wss' : 'ws'}://${host}:${port}/connect`;
	let ws = new WebSocket(url);
	if (parcel.requireWebSocketReload) {
		ws.oneShotChannels = parcel.websocketOneShotChannles;
		ws.channels = parcel.websocketPersistentChannels;
		parcel.requireWebSocketReload = false;
	} else {
		ws.oneShotChannels = {};
		ws.channels = {};
	}
	ws.captureNextPromise = function (type) {
		return new Promise(resolve => {
			ws.captureNext(type, (packet) => {
				resolve(packet);
			});
		});
	};
	ws.captureNext = function (type, handler) {
		let channel = ws.oneShotChannels[type] || [];
		channel.push(handler);
		ws.oneShotChannels[type] = channel;
	};
	ws.captureAll = function (type, handler) {
		let channel = ws.channels[type] || [];
		channel.push(handler);
		ws.channels[type] = channel;
	};
	ws.clearCaptureChannel = function () {
		ws.oneShotChannels = [];
		ws.channels = [];
	};
	ws.onmessage = function (ev) {
		let data = ev.data; // blob
		data.arrayBuffer().then(buf => {
			let packet = new IncomingPacket(buf);
			let oneShotChannel = ws.oneShotChannels[packet.type] || [];
			let persistentChannel = ws.channels[packet.type] || [];
			oneShotChannel.forEach(handler => {
				handler(packet);
			});
			this.oneShotChannels[packet.type] = [];
			persistentChannel.forEach(handler => {
				handler(packet);
			});
		});
	};
	ws.onerror = function (ev) {
		console.log(ev);
		parcel.requireWebSocketReload = true;
		parcel.websocketOneShotChannles = ws.oneShotChannels;
		parcel.websocketPersistentChannels = ws.channels;
		if (parcel.heartbeatTimer) {
			clearInterval(parcel.heartbeatTimer);
		}
		toast('与服务器通信出现错误, 5 秒后重连');
		setTimeout(startConnection, 5000);
	};
	ws.onclose = function () {
		if (parcel.reconnect) {
			startConnection();
		}
	};
	parcel.ws = ws;
}

async function messageHandler(packet) {
	if (parcel.privateKey) {
		let view = packet.data;
		let sender = view.getUint32(0);
		let tornString = tearString(view, 4);
		let encryptedMsg = tornString.str;
		let info = await getUserInfoOf(sender);
		if (!info) {
			console.log('陌生人的信息被丢弃了');
		} else {
			try {
				let msg = JSON.parse(parcel.privateKey.decrypt(encryptedMsg));
				dyalogAppend(sender, msg);
			} catch (e) {
				console.log(e);
				console.log('对方使用的密钥不正确');
			}
		}
	} else {
		console.log('在获得密钥前接收的信息被丢弃了');
	}
}

function heartbeatHandler(packet) {
	let status = packet.data.getUint8(0);
	if (status !== 0) {
		if (status === 255) {
			toast(`登录信息已过期，请重新登录`);
			setTimeout(() => {
				location.reload();
			}, 2000);
		} else {
			toast(`与服务器通信时发生错误: ${status}`);
		}
	}
}

function deathHandler(packet) {
	console.log(packet);
}

function tearShortString(data, offset) {
	let length = data.getUint16(offset);
	let str = tearStringWorker(data, offset + 2, length);
	return {
		str: str,
		offset: offset + length + 2
	};
}

function tearString(data, offset) {
	let length = data.getUint32(offset);
	let str = tearStringWorker(data, offset + 4, length);
	return {
		str: str,
		offset: offset + length + 4
	};
}

function tearStringWorker(data, offset, length) {
	console.log(`decoding string of length ${length} with offset ${offset}`);
	let decoder = new TextDecoder();
	decoder.ignoreBOM = true;
	let str = decoder.decode(data.buffer.slice(offset + data.byteOffset, offset + data.byteOffset + length));
	if (decoder.fatal) {
		throw new Error('Illegal byte found in stream. Decoding ended prematurely');
	}
	return str;
}

function Packet(type, length) {
	this.type = type;
	this.length = length;
	this.data = new ArrayBuffer(length);
	this.view = new DataView(this.data);
	this.offset = 0;

	this.appendByte = function (byte) {
		if (byte < 0 || byte > 0xff) {
			return;
		}
		this.view.setUint8(this.offset++, byte);
	};

	this.append = function (data) {
		for (let i = 0; i < data.length; i++) {
			this.appendByte(data[i]);
		}
	};

	this.appendShort = function (short) {
		if (short < 0 || short > 0xffff) {
			return;
		}
		this.view.setUint16(this.offset, short);
		this.offset += 2;
	};

	this.appendInt = function (num) {
		this.view.setUint32(this.offset, num);
		this.offset += 4;
	};

	this.appendShortString = function (str) {
		if (str.length > 0xff) {
			return;
		}
		this.appendShort(str.length);
		this.append((new TextEncoder()).encode(str));
	};

	this.appendString = function (str) {
		this.appendInt(str.length);
		this.append((new TextEncoder()).encode(str));
	};

	this.appendToken = function (token) {
		this.append((new TextEncoder()).encode(token));
	};

	this.pack = function () {
		let buffer = new ArrayBuffer(this.data.byteLength + 5);
		let view = new DataView(buffer);
		view.setUint8(0, this.type);
		view.setUint32(1, this.data.byteLength);
		let copySrc = new Uint8Array(this.data);
		let copyDst = new Uint8Array(buffer, 5);
		copyDst.set(copySrc);
		return buffer;
	};
}

function IncomingPacket(ab) {
	this.buffer = ab;
	this._data = new DataView(ab);
	this.type = this._data.getUint8(0);
	this.length = this._data.getUint32(1);
	this.data = new DataView(ab, 5, ab.byteLength - 5);
}

async function heartbeat() {
	if (parcel.ws && parcel.token) {
		let token = parcel.token;
		let now = Date.now();
		let packet = new Packet(0, 20);
		packet.appendToken(token);
		packet.appendInt(now);
		parcel.ws.send(packet.pack());
	}
}

async function register(username, password) {
	let keypair = new rsa({b: 1024});
	let publicKey = keypair.exportKey('pkcs8-public-pem');
	let privateKey = keypair.exportKey('pkcs8-private-pem');
	let encryptedPrivateKey = crypto.AES.encrypt(privateKey, password).toString();
	if (parcel.ws) {
		parcel.ws.captureNext(0x40, (packet) => {
			console.log(packet);
			let status = packet.data.getUint8(0);
			if (status === 0) {
				toast('注册成功');
				setTimeout(function () {
					goto('index', '登录');
				});
			} else {
				const errorMsg = {
					1: '用户名已存在',
					2: '数据包损坏',
					3: '注册过于频繁'
				};
				toast(errorMsg[status] || '未知错误: ' + status);
			}
		});
		let len = 10 + username.length + publicKey.length + encryptedPrivateKey.length;
		let packet = new Packet(1, len);
		packet.appendShortString(username);
		packet.appendString(publicKey);
		packet.appendString(encryptedPrivateKey);
		parcel.ws.send(packet.pack());
	} else {
		throw new Error('WebSocket not ready');
	}
}

function login(username, password) {
	let packet = new Packet(2, 2 + username.length);
	packet.appendShortString(username);
	if (parcel.ws) {
		parcel.ws.captureNext(0x41, (packet) => {
			let status = packet.data.getUint8(0);
			if (status === 0) {
				let uid = packet.data.getUint32(1);
				let tornString = tearString(packet.data, 5);
				let encryptedToken = tornString.str;
				tornString = tearString(packet.data, tornString.offset);
				let publicKey = tornString.str;
				tornString = tearString(packet.data, tornString.offset);
				let encryptedPrivateKey = tornString.str;
				let isIntrovert = packet.data.getUint8(tornString.offset);
				try {
					let privateKeyGlob = crypto.AES.decrypt(encryptedPrivateKey, password);
					let privateKey = privateKeyGlob.toString(crypto.enc.Utf8);
					let pk = new rsa(privateKey, 'pkcs8');
					let token = pk.decrypt(encryptedToken, 'utf8');
					parcel.uid = uid;
					parcel.publicKey = publicKey;
					parcel.privateKey = pk;
					parcel.token = token;
					document.getElementById('self-name').value = username;
					document.getElementById('self-uid').value = uid;
					document.getElementById('self-introvert').checked = isIntrovert === 0;
					componentHandler.upgradeDom();
					toast('登录成功');
					parcel.ws.captureAll(0x43, messageHandler);
					parcel.ws.captureAll(0x50, heartbeatHandler);
					parcel.ws.captureAll(0xff, deathHandler);
					// setup further stuff
					setTimeout(async () => {
						// start heartbeat
						parcel.heartbeatTimer = setInterval(heartbeat, 5000);
						// prepare ui
						document.getElementById('menu-button').classList.remove('hidden');
						goto('chat', 'HELOWRD');
						populateContacts();
						fetchChatHistory();
						// note to future self: might have to maintain a separate database
						// that contains plain encrypted (i.e. AES encrypted) messages
					}, 1500);
				} catch (ignored) {
					toast('用户名或密码错误');
				}
			} else {
				const errorMsg = {
					1: '用户名不存在',
					2: '此账户已被停用'
				};
				toast(errorMsg[status] || `未知错误: ${status}`);
			}
		});
		parcel.ws.send(packet.pack());
	} else {
		toast('未与服务器连接');
	}
}

function modifyInfo(newName, isIntrovert) {
	if (parcel.ws && parcel.token) {
		let totalLength = 19;
		let encodedUsername = (new TextEncoder()).encode(newName);
		let packet = new Packet(0x05, totalLength + encodedUsername.byteLength);
		packet.appendToken(parcel.token);
		packet.appendShortString(encodedUsername);
		packet.appendByte(isIntrovert ? 1 : 0);
		parcel.ws.captureNext(0x40, (packet) => {
			let status = packet.getUint8(0);
			if (status > 0) {
				toast(`修改失败：${status}`);
			} else {
				toast('修改成功');
				closeDialog();
			}
		})
	} else {
		toast('尚未登录');
	}
}

function fetchChatHistory() {
	if (parcel.ws && parcel.token) {
		let packet = new Packet(0x08, 24);
		packet.appendToken(parcel.token);
		packet.appendInt(getLastLoginTime());
		packet.appendInt(Date.now());
		// the message handler shall be set up
		// no need to setup handler again here
		parcel.ws.send(packet.pack());
	}
}

function populateContacts() {
	if (parcel.ws && parcel.token) {
		let packet = new Packet(0x07, 16);
		let token = parcel.token;
		packet.appendToken(token);
		parcel.ws.captureNext(0x44, (packet) => {
			let view = packet.data;
			let status = view.getUint8(0);
			if (status === 0) {
				let box = document.getElementById('contacts-container');
				let count = view.getUint32(1);
				if (count === 0) {
					//!(box.lastChild ? box.removeChild(box.lastChild) : undefined);
					console.log('Wow! So Empty!');
					// todo: wow! so empty!
				} else {
					// generate entries and populate
					let wrapper = document.createElement('div');
					wrapper.className = 'mdl-list';
					let arr = [];
					let offset = 5;
					let tornString;
					while (offset < view.byteLength) {
						let uid = view.getUint32(offset);
						let isOnline = view.getUint8(offset + 4);
						tornString = tearShortString(view, offset + 5);
						let name = tornString.str;
						tornString = tearString(view, tornString.offset);
						let publicKey = tornString.str;
						offset = tornString.offset;
						arr.push({
							uid: uid,
							isOnline: isOnline,
							name: name,
							publicKey: publicKey
						});
					}
					parcel.contacts = arr;
					let contacts = arr.map(obj => contactGenerator(obj));
					contacts.forEach(piece => wrapper.appendChild(piece));
					!(box.lastChild ? box.removeChild(box.lastChild) : undefined);
					box.appendChild(wrapper);
				}
			} else {
				const errorMsg = {
					255: '请重新登录'
				};
				toast(errorMsg[status] || `未知错误: ${status}`);
			}
		});
		parcel.ws.send(packet.pack());
	} else {
		toast('请先登录');
	}
}

function contactGenerator(obj) {
	let wrapper = document.createElement('div');
	wrapper.className = 'mdl-list__item mdl-list__item--two-line';
	wrapper.tabIndex = -1;
	let info = document.createElement('span');
	info.className = 'mdl-list__item-primary-content';
	let avatar = document.createElement('i'); // future: get avatar working
	avatar.className = 'material-icons mdl-list__item-avatar';
	avatar.innerText = 'person';
	let name = document.createElement('span');
	name.innerText = obj.name;
	name.setAttribute('data-key', 'username');
	name.setAttribute('data-uid', obj.uid);
	let online = document.createElement('span');
	online.className = 'mdl-list__item-sub-title';
	online.innerText = obj.isOnline ? 'Online' : 'Offline'; // future: get i18n working
	online.setAttribute('data-uid', obj.uid);
	online.setAttribute('data-key', 'online');
	info.appendChild(avatar);
	info.appendChild(name);
	info.appendChild(online);
	let chipBox = document.createElement('span');
	chipBox.className = 'mdl-list__item-secondary-action';
	let chip = document.createElement('span');
	chip.className = 'mdl-chip hidden';
	let chipText = document.createElement('span');
	chipText.className = 'mdl-chip__text';
	chipText.innerText = '0';
	chipText.setAttribute('data-key', 'unreadCount');
	chipText.setAttribute('data-uid', obj.uid);
	chip.appendChild(chipText);
	chipBox.appendChild(chip);
	wrapper.appendChild(info);
	wrapper.appendChild(chipBox);
	wrapper.onclick = function () {
		setTitle(obj.name);
		dyalogSelect(obj.uid);
	};
	return wrapper;
}

async function modifyContacts(uid, mode) {
	try {
		if (uid === parcel.uid) {
			closeDialog();
			toast('您不能添加或者删除自己');
			return;
		}
		let packet = new Packet(0x03, 21);
		packet.appendToken(parcel.token);
		packet.appendInt(uid);
		packet.appendByte(mode);
		parcel.ws.captureNext(0x40, (packet) => {
			closeDialog();
			let status = packet.data.getUint8(0);
			if (status !== 0) {
				const msg = {
					1: '您已经是对方好友',
					2: '用户不存在',
					3: '用户拒绝任何人添加其为好友',
					255: '需要重新登录'
				};
				toast(msg[status] || `未知错误: ${status}`);
			} else {
				toast('添加成功');
				populateContacts();
			}
		});
		parcel.ws.send(packet.pack());
	} catch (e) {
		closeDialog();
		toast('请输入 UID');
	}
}

async function getUserInfoOf(uid) {
	if (parcel.ws) {
		if (parcel.contacts) {
			let result = parcel.contacts.find(value => value.uid && value.uid === uid);
			if (!result) {
				// find over ethernet
				let packet = await parcel.ws.captureNextPromise(0x42);
				let view = packet.data;
				let status = view.getUint8(0);
				if (status === 0) {
					let uid = view.getUint32(1);
					let status = view.getUint32(5);
					let tornString = tearShortString(view, 6);
					let username = tornString.str;
					tornString = tearString(view, tornString.offset);
					let publicKey = tornString.str;
					let user = {
						uid: uid,
						isOnline: status,
						name: username,
						publicKey: publicKey
					};
					this.parcel.contacts.push(user);
					return user;
				} else {
					toast('获取联系人信息失败');
					return undefined;
				}
			} else {
				return result;
			}
		} else {
			// might not be cached? not logged in but the message has come through?
			toast('出现了未知错误');
			return undefined;
		}
	} else {
		toast('未连接到服务器');
		return undefined;
	}
}

function writeMessage(recipientId, msgObj) {
	return new Promise((resolve) => {
		if (parcel.ws) {
			if (parcel.token) {
				getUserInfoOf(recipientId).then(recipient => {
					let encryptor = new rsa();
					encryptor.importKey(recipient.publicKey);
					let encryptedMessage = encryptor.encrypt(JSON.stringify(msgObj), 'base64');
					let packet = new Packet(0x0a, 24 + encryptedMessage.length);
					packet.appendToken(parcel.token);
					packet.appendInt(recipientId);
					packet.appendString(encryptedMessage);
					parcel.ws.captureNext(0x40, (packet) => {
						let status = packet.data.getUint8(0);
						if (status !== 0) {
							if (status === 255) {
								toast('需要重新登录');
							} else {
								toast(`未知错误: ${status}`);
							}
						}
						resolve(status === 0);
					});
					parcel.ws.send(packet.pack());
				});
			} else {
				toast('需要登录');
				resolve(false);
			}
		} else {
			toast('未连接到服务器');
			resolve(false);
		}
	});
}

function wtf() {
	parcel.ws.captureNext(0xff, (packet) => {
		console.log(packet);
		console.log(tearString(packet.data, 0));
	});
	let packet = new Packet(0xff, 0);
	parcel.ws.send(packet.pack());
}

function downloadFile(percentageDom, name, token) {
	toast('开始传输文件...');
	percentageDom.innerText = '(0%)';
	let server = getServer();
	let xhr = new XMLHttpRequest();
	xhr.responseType = 'blob';
	xhr.onload = (e) => {
		if (xhr.status === 200) {
			let blob = new Blob([xhr.response]);
			let a = document.createElement("a");
			a.href = window.URL.createObjectURL(blob);
			a.download = name;
			a.click();
			percentageDom.innerText = '(100%)';
			window.URL.revokeObjectURL(a.href);
		} else {
			toast('文件已失效');
		}
	};
	xhr.onprogress = (e) => {
		if (e.lengthComputable) {
			let percentage = (100 * e.loaded / e.total).toFixed(1);
			percentageDom.innerText = `(${percentage}%)`;
		} else {
			percentageDom.innerText = '(未知长度)';
		}
	};
	xhr.onerror = () => {
		toast('文件服务暂不可用');
	};
	xhr.open('GET', `${server.secure ? 'https' : 'http'}://${server.addr}:${server.port}/file?token=${token}`);
	xhr.send();
}
